Skip to content

Conversation

@Sheraff
Copy link
Contributor

@Sheraff Sheraff commented Jan 22, 2026

To run the benchmark

cd e2e/react-start/flamegraph-bench
pnpm bench

This will

  • build the test app in production mode
  • in parallel
    • serve this express app (tests/server.js) through @platformatic/flame for perf measurements
    • request pages (tests/bench.js) using autocannon for load testing

This results in a load test summary by autocannon

[1] ┌─────────┬───────┬────────┬────────┬────────┬───────────┬───────────┬─────────┐
[1] │ Stat    │ 2.5%  │ 50%    │ 97.5%  │ 99%    │ Avg       │ Stdev     │ Max     │
[1] ├─────────┼───────┼────────┼────────┼────────┼───────────┼───────────┼─────────┤
[1] │ Latency │ 43 ms │ 146 ms │ 204 ms │ 214 ms │ 143.88 ms │ 136.84 ms │ 4267 ms │
[1] └─────────┴───────┴────────┴────────┴────────┴───────────┴───────────┴─────────┘
[1] ┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬────────┬─────────┐
[1] │ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg     │ Stdev  │ Min     │
[1] ├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤
[1] │ Req/Sec   │ 530     │ 530     │ 699     │ 727     │ 691.24  │ 35.02  │ 530     │
[1] ├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤
[1] │ Bytes/Sec │ 7.16 MB │ 7.16 MB │ 9.45 MB │ 9.82 MB │ 9.34 MB │ 473 kB │ 7.16 MB │
[1] └───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────┴─────────┘
[1] 
[1] Req/Bytes counts sampled once per second.
[1] # of samples: 30
[1] 
[1] 21k requests in 30.02s, 280 MB read
[1] 
[1] === SSR Benchmark Results ===
[1] Total requests: 20737
[1] Requests/sec: 691.24
[1] Latency (avg): 143.88ms
[1] Latency (p99): 214ms
[1] Throughput: 8.90 MB/s

And flame generates:

  • cpu-profile in html (open in browser), in markdown (give to AI), in protobuf (open in speedscope.app)
  • heap-profile in the same versions

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a new flamegraph benchmarking and profiling project for measuring application performance.
  • Tests

    • Introduced benchmarking scripts that measure request throughput, latency percentiles, and performance metrics under load.
    • Added server profiling configuration for concurrent connection testing and performance measurement.
  • Chores

    • Added project configuration files and build infrastructure for the benchmarking environment.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2026

📝 Walkthrough

Walkthrough

Adds a new end-to-end flamegraph benchmarking project at e2e/react-start/flamegraph-bench with React Start app scaffolding, routing, client/server entries, Vite/TS configs, benchmark and server scripts, and .gitignore rules for profiling artifacts.

Changes

Cohort / File(s) Summary
Project config & tooling
e2e/react-start/flamegraph-bench/.gitignore, e2e/react-start/flamegraph-bench/package.json, e2e/react-start/flamegraph-bench/tsconfig.json, e2e/react-start/flamegraph-bench/vite.config.ts
New project manifest, scripts for build/start/profile/bench, TypeScript config (ES2022/strict), Vite config with TanStack Start and React plugins, and gitignore for flamegraph artifacts.
Routing core
e2e/react-start/flamegraph-bench/src/routeTree.gen.ts, e2e/react-start/flamegraph-bench/src/router.tsx
Adds generated route tree (index, /page/$id) with types and a getRouter() factory that creates the router with scroll restoration.
Route files
e2e/react-start/flamegraph-bench/src/routes/__root.tsx, e2e/react-start/flamegraph-bench/src/routes/index.tsx, e2e/react-start/flamegraph-bench/src/routes/page.$id.tsx
Root layout component and two file routes: index (renders 10 links) and page.$id (renders header + 100 parameterized links).
Client & Server entry points
e2e/react-start/flamegraph-bench/src/client.tsx, e2e/react-start/flamegraph-bench/src/server.ts
Client hydration using hydrateRoot + StartClient; server export delegating fetch to React Start handler.
Benchmark & local test server
e2e/react-start/flamegraph-bench/tests/bench.js, e2e/react-start/flamegraph-bench/tests/server.js
Autocannon-based bench script targeting localhost:3000 and an Express wrapper that loads built server, converts fetch handler to Node handler, and serves static client assets.

Sequence Diagram(s)

sequenceDiagram
  participant B as "Benchmark (autocannon)"
  participant E as "Express test server\n(dist wrapper)"
  participant N as "Node handler\n(toNodeHandler)"
  participant S as "React Start server\n(handler.fetch)"
  participant R as "Router / SSR\n(routeTree)"
  participant FS as "Static assets\n(dist/client)"
  rect rgba(200,200,255,0.5)
    B->>E: HTTP requests (random /page/:id)
  end
  rect rgba(200,255,200,0.5)
    E->>N: forward request
    N->>S: call handler.fetch(request)
    S->>R: render route (SSR)
    R-->>S: rendered HTML
    S-->>N: response
    N-->>E: response
    E-->>B: response (HTML or asset)
  end
  opt static asset hit
    E->>FS: serve static file
    FS-->>E: file bytes
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

package: react-start

Suggested reviewers

  • schiller-manuel
  • tannerlinsley

Poem

🐰 In burrows of code I nimbly trot,
Routes and pages arranged in a plot,
Benchmarks blaze and flamegraphs sing,
SSR hops fast on a springtime wing,
Puff of profiled joy — performance caught!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main objective: adding a benchmarking setup to the React Start e2e tests using platformatic/flame and autocannon.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link

nx-cloud bot commented Jan 22, 2026

View your CI Pipeline Execution ↗ for commit ad7a1c3

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 12m 30s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1m View ↗

☁️ Nx Cloud last updated this comment at 2026-01-22 20:54:51 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 22, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@6457

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@6457

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@6457

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/nitro-v2-vite-plugin@6457

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@6457

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@6457

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@6457

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@6457

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@6457

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@6457

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@6457

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@6457

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@6457

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@6457

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@6457

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@6457

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@6457

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@6457

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@6457

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@6457

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@6457

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-ssr-query@6457

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@6457

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@6457

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@6457

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@6457

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-fn-stubs@6457

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@6457

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@6457

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-static-server-functions@6457

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@6457

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@6457

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@6457

@tanstack/vue-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router@6457

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router-devtools@6457

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router-ssr-query@6457

@tanstack/vue-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start@6457

@tanstack/vue-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-client@6457

@tanstack/vue-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-server@6457

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@6457

commit: ad7a1c3

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@e2e/react-start/flamegraph-bench/package.json`:
- Around line 13-14: Replace the internal workspace dependency versions that
currently use the caret protocol with the workspace wildcard protocol: update
the entries for "@tanstack/react-router" and "@tanstack/react-start" in
package.json to use "workspace:*" instead of "workspace:^" so they conform to
the internal dependency guideline.

In `@e2e/react-start/flamegraph-bench/src/routes/index.tsx`:
- Line 1: The import order violates ESLint's alphabetical rule: swap the named
imports so Link appears before createFileRoute in the import statement from
'@tanstack/react-router' (i.e., change the import to import { Link,
createFileRoute } from '@tanstack/react-router') to satisfy the linter and keep
the symbols Link and createFileRoute correctly referenced.

In `@e2e/react-start/flamegraph-bench/src/routes/page`.$id.tsx:
- Line 1: The import specifiers are out of alphabetical order: change the named
imports in the top-level import from "createFileRoute, Link" to have "Link"
before "createFileRoute" so the statement reads with Link first; update the
import line that currently references createFileRoute and Link accordingly to
satisfy the ESLint rule.

In `@e2e/react-start/flamegraph-bench/tests/server.js`:
- Around line 4-13: The port and static path handling in createStartServer are
incorrect: coerce process.env.PORT to a number before using it (the port
variable / server.listen) and resolve the static asset path relative to this
module instead of the CWD for express.static; update the port assignment to
parseInt or Number(process.env.PORT || '3000') and change
express.static('../dist/client', ...) to use
path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../dist/client')
(referencing createStartServer, port, and the express.static call) so the server
listens on a TCP port and serves the correct build assets.
🧹 Nitpick comments (1)
e2e/react-start/flamegraph-bench/package.json (1)

21-21: Consider pinning @platformatic/flame to a specific version range.

Using "latest" for @platformatic/flame can lead to non-deterministic builds and potential breakage when new versions are released. For more stable and reproducible benchmarking results, consider pinning to a specific version or version range.

📌 Suggested approach
-    "@platformatic/flame": "latest",
+    "@platformatic/flame": "^1.0.0",

Note: Replace ^1.0.0 with the appropriate current version.

Comment on lines +4 to +13
const port = process.env.PORT || 3000

async function createStartServer() {
const server = (await import('../dist/server/server.js')).default
const nodeHandler = toNodeHandler(server.fetch)

const app = express()

// Serve static client assets (matching basic example pattern)
app.use(express.static('../dist/client', { redirect: false }))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find and read the server.js file
find . -name "server.js" -path "*/flamegraph-bench/tests/*" -type f

Repository: TanStack/router

Length of output: 109


🏁 Script executed:

# Check the file contents
cat -n e2e/react-start/flamegraph-bench/tests/server.js

Repository: TanStack/router

Length of output: 994


🏁 Script executed:

# Check the module type of the file (is it ESM or CommonJS?)
head -20 e2e/react-start/flamegraph-bench/tests/server.js

Repository: TanStack/router

Length of output: 610


🏁 Script executed:

# Check package.json to understand the module context
find e2e/react-start/flamegraph-bench -name "package.json" -type f | head -1 | xargs cat

Repository: TanStack/router

Length of output: 954


🌐 Web query:

express.static relative path resolution behavior

💡 Result:

Short answer: express.static resolves a relative root path relative to the current working directory (process.cwd()), i.e. where you launched node — not relative to the module file. Use an absolute path (e.g. path.join(__dirname, 'public')) so resolution is deterministic. [1][2]

Details (one line each):

  • express.static(root, ...) treats the root argument as the directory to serve; a string path is interpreted relative to the directory where the node process was started. [1]
  • Because require() and __dirname are module-relative but process.cwd() depends on how you start the app, using path.join(__dirname, '...') or path.resolve(...) is the recommended pattern. [1][2]

Example:

  • app.use('/static', express.static(path.join(__dirname, 'public'))). [1][2]

Sources:
[1] Express docs — Serving static files.
[2] StackOverflow explanation showing express.static uses the process launch directory.


🌐 Web query:

Node.js process.env.PORT string named pipe behavior

💡 Result:

Short answer: If process.env.PORT is a string that looks like a path (on Windows: begins with \?\pipe\ or \.\pipe), Node's server.listen treats it as an IPC path (named pipe) instead of a TCP port. A numeric string ("3000") is treated as a port number, but a pipe-style string is treated as an IPC path — so pass a Number (e.g. Number(process.env.PORT) or parseInt) when you mean a TCP port, or pass the pipe string when you want a named pipe. [1][2]

Example:

  • TCP port (ensure numeric):
    const port = Number(process.env.PORT) || 3000;
    server.listen(port);
  • Named pipe (Windows):
    const pipe = process.env.PORT; // e.g. "\\?\pipe\myapp"
    server.listen(pipe);

References:

  • Node.js net.server.listen (IPC / named-pipe behavior on Windows). [1]
  • Node.js docs for server.listen variations (port vs path). [2]

🏁 Script executed:

# Check the vite config to understand build output
find e2e/react-start/flamegraph-bench -name "vite.config.*" -o -name "tsconfig.*" | head -5

Repository: TanStack/router

Length of output: 153


🏁 Script executed:

# Look for build configuration and output pattern
find e2e/react-start/flamegraph-bench -type f \( -name "*.config.*" -o -name "*.json" \) | grep -E "(vite|build|tsconfig)" | head -10

Repository: TanStack/router

Length of output: 153


🏁 Script executed:

# Check what files exist in the flamegraph-bench directory structure
ls -la e2e/react-start/flamegraph-bench/ 2>/dev/null | head -20

Repository: TanStack/router

Length of output: 527


🏁 Script executed:

# Verify if there's a dist or build directory already
find e2e/react-start/flamegraph-bench -maxdepth 2 -type d -name "dist" -o -type d -name "build" 2>/dev/null

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Check the vite config to understand where dist is built
cat e2e/react-start/flamegraph-bench/vite.config.ts

Repository: TanStack/router

Length of output: 315


🏁 Script executed:

# Also check if there's a root-level vite config
cat e2e/react-start/vite.config.ts 2>/dev/null || cat vite.config.ts 2>/dev/null || echo "No root vite config found"

Repository: TanStack/router

Length of output: 84


🏁 Script executed:

# Check the actual build output by looking at how tanstackStart plugin configures output
# Let's search for dist references in the codebase to understand build structure
rg "dist/client|dist/server" e2e/react-start/flamegraph-bench/ --type ts --type js -A 2 -B 2

Repository: TanStack/router

Length of output: 878


🏁 Script executed:

# Check if there are any other references to how the dist is structured in the project
rg "import.*dist" e2e/react-start/flamegraph-bench/ --type ts --type js

Repository: TanStack/router

Length of output: 175


🏁 Script executed:

# Let's trace the actual build setup by checking package.json build command
cat e2e/react-start/flamegraph-bench/package.json | grep -A 10 "scripts"

Repository: TanStack/router

Length of output: 573


🏁 Script executed:

# Check if there's a monorepo root vite config or build setup
cat vite.config.ts 2>/dev/null | head -30

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Let's see what the actual directory structure shows - does a dist exist anywhere?
find e2e/react-start -maxdepth 3 -type d | grep -E "^e2e/react-start/(dist|flamegraph-bench)" | sort

Repository: TanStack/router

Length of output: 211


🏁 Script executed:

# Check if the coding guidelines mention ESLint router rules for this file
cat .eslintrc* 2>/dev/null | grep -i router || echo "No ESLint config found"

Repository: TanStack/router

Length of output: 81


🏁 Script executed:

# Check if there are ESLint rules configured anywhere
find . -name ".eslintrc*" -o -name "eslint.config.*" | head -5 | xargs cat 2>/dev/null

Repository: TanStack/router

Length of output: 4371


Coerce PORT to number and resolve static asset path relative to module location.

The code passes process.env.PORT as a string to server.listen(), which Node treats as an IPC pipe path on Windows instead of a TCP port. Additionally, express.static('../dist/client') resolves relative to the process working directory, so it points to the wrong location when the build output is in ./dist/client. Coerce PORT to a number and resolve the asset path relative to this file.

Proposed fix
 import { toNodeHandler } from 'srvx/node'
 import express from 'express'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'

-const port = process.env.PORT || 3000
+const port = Number(process.env.PORT) || 3000
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+const clientDir = path.resolve(__dirname, '../dist/client')

   // Serve static client assets (matching basic example pattern)
-  app.use(express.static('../dist/client', { redirect: false }))
+  app.use(express.static(clientDir, { redirect: false }))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const port = process.env.PORT || 3000
async function createStartServer() {
const server = (await import('../dist/server/server.js')).default
const nodeHandler = toNodeHandler(server.fetch)
const app = express()
// Serve static client assets (matching basic example pattern)
app.use(express.static('../dist/client', { redirect: false }))
import { toNodeHandler } from 'srvx/node'
import express from 'express'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const port = Number(process.env.PORT) || 3000
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const clientDir = path.resolve(__dirname, '../dist/client')
async function createStartServer() {
const server = (await import('../dist/server/server.js')).default
const nodeHandler = toNodeHandler(server.fetch)
const app = express()
// Serve static client assets (matching basic example pattern)
app.use(express.static(clientDir, { redirect: false }))
🤖 Prompt for AI Agents
In `@e2e/react-start/flamegraph-bench/tests/server.js` around lines 4 - 13, The
port and static path handling in createStartServer are incorrect: coerce
process.env.PORT to a number before using it (the port variable / server.listen)
and resolve the static asset path relative to this module instead of the CWD for
express.static; update the port assignment to parseInt or
Number(process.env.PORT || '3000') and change express.static('../dist/client',
...) to use path.resolve(path.dirname(fileURLToPath(import.meta.url)),
'../dist/client') (referencing createStartServer, port, and the express.static
call) so the server listens on a TCP port and serves the correct build assets.

@Sheraff Sheraff merged commit d0b472e into main Jan 22, 2026
6 checks passed
@Sheraff Sheraff deleted the test-react-start-benchmarking-setup branch January 22, 2026 20:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants